/**********************************************************************
*  Copyright (c) 2007 - Victor Jacobs - victor.jacobs@gmail.com
*
*  Permission is hereby granted, free of charge, to any person
*  obtaining a copy of this software and associated documentation
*  files (the "Software"), to deal in the Software without
*  restriction, including without limitation the rights to use,
*  copy, modify, merge, publish, distribute, sublicense, and/or sell
*  copies of the Software, and to permit persons to whom the
*  Software is furnished to do so, subject to the following
*  conditions:
*
*  The above copyright notice and this permission notice shall be
*  included in all copies or substantial portions of the Software.
*
*  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
*  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
*  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
*  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
*  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
*  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
*  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
*  OTHER DEALINGS IN THE SOFTWARE.
**********************************************************************/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Collections;
using System.Diagnostics;
using Vj.Math;
using Vj.GATool.Controls;
using Vj.GA;
using Vj.GA.Functions;

namespace Vj.GATool
{
    public delegate double AFunction(double[] values);

    public partial class GAForm : Form
    {
        private Plot3DPanel plot3DControl;
        private AFunction function3D;

        private GeneticAlg ga;
        private int popSize = 15;
        private float muRate = 0.005f;
        private float selRate = 0.67f;
        private int alleleLength = 8;

        bool twoDimMode = true;

        public GAForm()
        {
            InitializeComponent();

            functionComboBox.Text = "50-sqrt(x^2 + y^2)";
            functionComboBox.Items.Add("50-sqrt(x^2 + y^2)");
            functionComboBox.Items.Add("-(x^2+y^2)/100 + 50");
            functionComboBox.Items.Add("(x+y)/20");
            functionComboBox.Items.Add("0");
            functionComboBox.Items.Add("(x^2-y^2)/100");
            functionComboBox.Items.Add("10(Cos(10*x^2+y^2))");

            function3D = new AFunction(FunctionGrabber.Cone);

            crossoverComboBox.DataSource = System.Enum.GetValues(typeof(CrossoverOperators));
            selectionComboBox.DataSource = System.Enum.GetValues(typeof(SelectionOperators));
            mutationComboBox.DataSource = System.Enum.GetValues(typeof(MutationOperators));
            replacementComboBox.DataSource = System.Enum.GetValues(typeof(ReplacementOperators));
            terminationComboBox.DataSource = System.Enum.GetValues(typeof(TerminationOperators));

            plot3DControl = new Plot3DPanel(function3D);

            plot3DPanel.Controls.Add(plot3DControl);

            tabControl2.SelectedIndex = 2;

            stepButton.Enabled = false;
            evolveButton.Enabled = false;

            xRotTrackBar.Value = 5;
            yRotTrackBar.Value = -10;

            objectiveComboBox.SelectedIndex = 0;

            graphStatusLabel.Hide();

        }

        #region Main Button Handlers
        private void quitButton_Click(object sender, EventArgs e)
        {
            Close();
        }

        private void startButton_Click(object sender, EventArgs e)
        {
            plot3DControl.Function3D = function3D;
            plot3DControl.DoDrawData = true;
            tabControl2.SelectedIndex = 2;

            ga = new GeneticAlg();

            // eventually function3D and maxObjective should be combined into an object
            ga.FitnessFunction = new FitFunc(function3D);

            ga.Initiate(popSize, alleleLength, (int)numOfAllelesUpDown.Value);

            if(objectiveComboBox.Text == "Max")
                ga.Population.MaximizeObjective = true;
            else
                ga.Population.MaximizeObjective = false;

            ga.Population.Evaluate(ga.FitnessFunction);

            ga.SetOperators((SelectionOperators)selectionComboBox.SelectedValue,
                (MutationOperators)mutationComboBox.SelectedValue, (CrossoverOperators)crossoverComboBox.SelectedValue,
                (ReplacementOperators)replacementComboBox.SelectedValue, elitismCheckBox.Checked);

            if (twoDimMode)
                Draw3DPlotData(ga.GetPopulation());

            output1TextBox.Text = ga.ToString();

            stepButton.Enabled = true;
            evolveButton.Enabled = true;

            bestFitTextBox.Text = ga.Population.BestFitness.ToString();
            worstFitTextBox.Text = ga.Population.WorstFitness.ToString();
            avgFitTextBox.Text = ga.Population.AvgFitness.ToString();

            //int[] tmp = { 1, 1, 1 };
            //output1TextBox.Text += "\n";
            ////output1TextBox.Text += Binary.ToString(Binary.BinToGray(tmp));
            //output1TextBox.Text += Binary.ToString(tmp);
        }

        private void stopButton_Click(object sender, EventArgs e)
        {
            //plot2DControl.DoDrawFunction = false;
        }

        private void stepButton_Click(object sender, EventArgs e)
        {
            ga.DoStep(popSize, muRate, selRate);

            if (twoDimMode)
                Draw3DPlotData(ga.GetPopulation());

            output1TextBox.Text += "\n" + ga.ToString();
            output1TextBox.Focus();
            output1TextBox.SelectionStart = output1TextBox.Text.Length;
            output1TextBox.ScrollToCaret();

            bestFitTextBox.Text = ga.Population.BestFitness.ToString();
            worstFitTextBox.Text = ga.Population.WorstFitness.ToString();
            avgFitTextBox.Text = ga.Population.AvgFitness.ToString();
            //DrawData();
        }

        private void evolveButton_Click(object sender, EventArgs e)
        {
            int maxGen = 100;

            if ((TerminationOperators)terminationComboBox.SelectedValue == TerminationOperators.GenerationNumber)
                maxGen = (int) terminationParamNumericUpDown.Value;

            for (int genCt = 0; genCt < maxGen; genCt++)
            {
                ga.DoStep(popSize, muRate, selRate);

                if (twoDimMode)
                    Draw3DPlotData(ga.GetPopulation());

                output1TextBox.Text += "\n" + ga.ToString();
                output1TextBox.Focus();
                output1TextBox.SelectionStart = output1TextBox.Text.Length;
                output1TextBox.ScrollToCaret();

                bestFitTextBox.Text = ga.Population.BestFitness.ToString();
                worstFitTextBox.Text = ga.Population.WorstFitness.ToString();
                avgFitTextBox.Text = ga.Population.AvgFitness.ToString();
                this.Update();

                
                // - TerminationOperators.GenerationNumber -
                // A termination method that stops the evolution when the user-specified max 
                // number of evolutions have been run. This termination method is always active.
                
                // - TerminationOperators.PopulationConvergence - 
                // The population is deemed as converged when the average fitness across 
                // the current population is less than a user-specified percentage away 
                // from the best fitness of the current population.


                if ((TerminationOperators)terminationComboBox.SelectedValue == TerminationOperators.GenerationNumber &&
                    genCt >= (double)terminationParamNumericUpDown.Value)
                    return;
                else if ((TerminationOperators)terminationComboBox.SelectedValue == TerminationOperators.PopulationConvergence &&
                    ga.Population.AvgFitness < (double)terminationParamNumericUpDown.Value * (1 - ga.Population.BestFitness))
                    return;

            }

        }

        #endregion


        #region 3D Plot Options

        private void xRotTrackBar_ValueChanged(object sender, EventArgs e)
        {
            xRotLabel.Text = "X: " + xRotTrackBar.Value;
            plot3DControl.XRotAngle = xRotTrackBar.Value;
        }

        private void yRotTrackBar_ValueChanged(object sender, EventArgs e)
        {
            yRotLabel.Text = "Y: " + yRotTrackBar.Value;
            plot3DControl.YRotAngle = yRotTrackBar.Value;
        }

        private void reset3dPlotValueButton_Click(object sender, EventArgs e)
        {
            xRotTrackBar.Value = 0;
            yRotTrackBar.Value = 0;
        }

        private void lowRadioButton_CheckedChanged(object sender, EventArgs e)
        {
            plot3DControl.SetLowResolution();
        }

        private void avgRadioButton_CheckedChanged(object sender, EventArgs e)
        {
            plot3DControl.SetAverageResolution();
        }

        private void highRadioButton_CheckedChanged(object sender, EventArgs e)
        {
            plot3DControl.SetHighResolution();
        }

        private void drawPlotCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            if (drawPlotCheckBox.Checked)
                plot3DControl.DoDrawFunction = true;
            else
                plot3DControl.DoDrawFunction = false;
        }

        private void drawAxisCheckBox_CheckedChanged(object sender, EventArgs e)
        {
            if (drawAxisCheckBox.Checked)
                plot3DControl.DoDrawAxis = true;
            else
                plot3DControl.DoDrawAxis = false;
        }
        #endregion

        void Draw3DPlotData(Population aPop)
        {
            ArrayList dataList = new ArrayList();
            Genome chrom;
            for (int i = 0; i < ga.PopSize; i++)
            {
                chrom = aPop.GetGenome(i);
                dataList.Add(new Vector(chrom.AlleleValuesD[0], chrom.Fitness,
                    chrom.AlleleValuesD[1]));
            }

            plot3DControl.ClearData();
            plot3DControl.SetData(dataList);
        }

        private void popSizeUpDown_ValueChanged(object sender, EventArgs e)
        {
            this.popSize = (int)popSizeUpDown.Value;
        }

        private void selRateUpDown_ValueChanged(object sender, EventArgs e)
        {
            this.selRate = (float)selRateUpDown.Value;
        }

        private void muRateUpDown_ValueChanged(object sender, EventArgs e)
        {
            this.muRate = (float)muRateUpDown.Value;
        }

        private void functionComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {

            if (functionComboBox.Text.Equals("50-sqrt(x^2 + y^2)"))
            {
                function3D = new AFunction(FunctionGrabber.Cone);
            }
            else if (functionComboBox.Text.Equals("-(x^2+y^2)/100 + 50"))
            {
                function3D = new AFunction(FunctionGrabber.Dome);
            }
            else if (functionComboBox.Text.Equals("(x+y)/20"))
            {
                function3D = new AFunction(FunctionGrabber.Linear1);
            }
            else if (functionComboBox.Text.Equals("0"))
            {
                function3D = new AFunction(FunctionGrabber.Linear2);
            }
            else if (functionComboBox.Text.Equals("(x^2-y^2)/100"))
            {
                function3D = new AFunction(FunctionGrabber.SaddlePoint);
            }
            else if (functionComboBox.Text.Equals("25(Cos(x/16) + Cos(y/16))"))
            {
                function3D = new AFunction(FunctionGrabber.VeryWavy);
            }
            else if (functionComboBox.Text.Equals("10(Cos(10*x^2+y^2))"))
            {
                function3D = new AFunction(FunctionGrabber.Wavy);
            }

            plot3DControl.Function3D = function3D;
            plot3DControl.ClearData();
            plot3DControl.Refresh();
            stepButton.Enabled = false;

        }

        private void alleleLengthUpDown_ValueChanged(object sender, EventArgs e)
        {
            alleleLength = (int)alleleLengthUpDown.Value;
        }

        private void numOfAllelesUpDown_ValueChanged(object sender, EventArgs e)
        {
            if (numOfAllelesUpDown.Value == 2)
            {
                twoDimMode = true;
                plot3DPanel.Show();
                graphStatusLabel.Text = "";
            }
            else
            {
                twoDimMode = false;
                graphStatusLabel.Text = "Graph currently only available with 2D genomes.";
                graphStatusLabel.Show();
                plot3DPanel.Hide();
            }


        }

        private void terminationComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            if ((TerminationOperators)terminationComboBox.SelectedValue == TerminationOperators.GenerationNumber)
            {
                terminationParamNumericUpDown.DecimalPlaces = 0;
                terminationParamNumericUpDown.Increment = 5;
                terminationParamNumericUpDown.Minimum = 1;
                terminationParamNumericUpDown.Maximum = 1000;
                terminationParamNumericUpDown.Value = 10;
            }
            if ((TerminationOperators)terminationComboBox.SelectedValue == TerminationOperators.PopulationConvergence)
            {
                terminationParamNumericUpDown.DecimalPlaces = 2;
                terminationParamNumericUpDown.Increment = (decimal)0.01;
                terminationParamNumericUpDown.Minimum = 0;
                terminationParamNumericUpDown.Maximum = 1;
                terminationParamNumericUpDown.Value = (decimal)0.1;
            }
            //else if ((TerminationOperators)terminationComboBox.SelectedValue == TerminationOperators.)
            //{
            //    terminationParamNumericUpDown.DecimalPlaces = 0;
            //    terminationParamNumericUpDown.Increment = 5;
            //    terminationParamNumericUpDown.Minimum = 1;
            //    terminationParamNumericUpDown.Maximum = 1000;
            //    terminationParamNumericUpDown.Value = 10;
            //}





        }
 


    }
}